package jssia.database.verification;

import java.util.Arrays;

import jssia.database.ColumnInfo;
import jssia.database.IndexInfo;
import jssia.database.TableInfo;
import jssia.database.mysql.columns.Column;
import jssia.database.mysql.columns.DecimalColumn;
import jssia.database.mysql.columns.EnumBackedColumn;
import jssia.database.mysql.columns.StringColumn;
import jssia.database.mysql.constants.IndexType;

import com.smoed.common.utilities.FrameworkUtils;

final class SchemaVerificationUtils {
    private SchemaVerificationUtils() {
    }

    private static final ColumnInfo DEFAULT_COLUMN_INFO = FrameworkUtils.getAnnotationDefault(ColumnInfo.class);

    static void verifyTableSchema(TableInfo tableInfo, TableSchema tableSchema) {
        if (!tableInfo.engine().equals(tableSchema.getEngine()))
                throw new IllegalStateException("The engine (" + tableInfo.engine() + ") defined in source for table '"
                        + tableSchema.getName() + "' does not match the engine (" + tableSchema.getEngine()
                        + ") defined by the database server.");
        if (!tableInfo.collation().equals(tableSchema.getCollation()))
            throw new IllegalStateException("The collation (" + tableInfo.collation() + ") defined in source for table '"
                    + tableSchema.getName() + "' does not match the collation (" + tableSchema.getCollation()
                    + ") defined by the database server.");
    }

    static void verifyColumnSchema(Column column, ColumnInfo columnInfo, ColumnSchema colSchema) {
        if (column.getClass() != colSchema.getColumnType())
            throw new IllegalStateException("The type (" + column.getClass().getSimpleName() + ") defined in source for column '"
                    + column + "' does not match the type (" + colSchema.getColumnType().getSimpleName()
                    + ") defined by the database server.");

        if (columnInfo == null)
            columnInfo = DEFAULT_COLUMN_INFO;

        if (colSchema.getCollation() != null && !columnInfo.collation().equals(colSchema.getCollation()))
            throw new IllegalStateException("The collation (" + columnInfo.collation() + ") defined in source for column '"
                    + column + "' does not match the type (" + colSchema.getCollation()
                    + ") defined by the database server.");
        if (!((columnInfo.nullable() && colSchema.isNullable()) || (!columnInfo.nullable() && !colSchema.isNullable())))
            throw new IllegalStateException("The column '" + column + "' is defined as " + ((columnInfo.nullable()) ? "" : "not ")
                    + "nullable in source, but is defined as " + ((colSchema.isNullable()) ? "" : "not ") + "nullable in the server.");
        if ((!columnInfo.autoIncrement() && colSchema.isAutoIncrement()) || (columnInfo.autoIncrement() && !colSchema.isAutoIncrement()))
            throw new IllegalStateException("The column '" + column + "' is defined as " + ((columnInfo.nullable()) ? "" : "not ")
                    + "auto increment in source, but is defined to " + ((colSchema.isNullable()) ? "" : "not ") + "auto increment in the server.");
        if (!columnInfo.defaultValue().equalsIgnoreCase(colSchema.getDefaultValue()))
            throw new IllegalStateException("The default value (" + columnInfo.defaultValue() + ") defined in source for column '"
                    + column + "' does not match the default value (" + colSchema.getDefaultValue()
                    + ") defined by the database server.");

        if (column instanceof StringColumn) {
            StringColumn charCol = (StringColumn) column;
            if (charCol.getMaxLength() != colSchema.getLength().longValue())
                throw new IllegalStateException("The maximum character length of the column '" + column + "' in source is defined as "
                        + charCol.getMaxLength() + ", but the server has it defined as " + colSchema.getLength());
        } else if (column instanceof EnumBackedColumn) {
            EnumBackedColumn enumBackedColumn = (EnumBackedColumn) column;
            if (!enumBackedColumn.getValues().equals(colSchema.getValueSet()))
                throw new IllegalStateException("The enum values defined for column '" + column
                        + "' do match the values defined by the database server.\n"
                        + enumBackedColumn.getValues() + " does not equal " + colSchema.getValueSet()
                        + "\nNote: Order and case do NOT matter.");
        } else if (column instanceof DecimalColumn) {
            DecimalColumn decCol = (DecimalColumn) column;
            if (decCol.getPrecision() != colSchema.getPrecision() || decCol.getScale() != colSchema.getScale())
                throw new IllegalStateException("The precision and scale defined in source ("
                        + decCol.getPrecision() + ", " + decCol.getScale() + ") "
                        + " does not match the precision and scale defined by the server ("
                        + colSchema.getPrecision() + ", " + colSchema.getScale() + ").");
        }

        final IndexInfo indexInfo = columnInfo.index();
        final IndexSchema indexSchema = colSchema.getIndex();
        verifyIndex(indexInfo, indexSchema, column.toString());
    }

    private static void verifyIndex(IndexInfo indexInfo, IndexSchema indexSchema, String columnName) {
        if (indexInfo.type().equals(IndexType.NONE) && indexSchema == null)
            return;

        if (!indexInfo.type().equals(IndexType.NONE) && indexSchema == null)
            throw new IllegalStateException("The index " + indexInfo + " defined in source for column '"
                    + columnName + "' is not defined by the database server.");

        if (indexInfo.type().equals(IndexType.NONE) && indexSchema != null)
            throw new IllegalStateException("The index '" + indexSchema
                    + "' defined by the database server is not defined in source for column '"
                    + columnName);

        if (!indexInfo.name().equalsIgnoreCase(indexSchema.getName()))
            throw new IllegalStateException("The index name '" + indexInfo.name() + "' defined in source for column '"
                    + columnName + "' does not match the name '" + indexSchema.getName()
                    + "' defined by the database server.");

        if (!indexInfo.type().equals(indexSchema.getType()))
            throw new IllegalStateException("The index type (" + indexInfo.type() + ") defined in source for column '"
                    + columnName + "' does not match the type (" + indexSchema.getType()
                    + ") defined by the database server.");

        if (!indexInfo.structure().equals(indexSchema.getStructure()))
            throw new IllegalStateException("The index structure (" + indexInfo.structure() + ") defined in source for column '"
                    + columnName + "' does not match the structure (" + indexSchema.getStructure()
                    + ") defined by the database server.");

        if (!Arrays.equals(indexInfo.columns(), indexSchema.getColumns())) {
            throw new IllegalStateException("The columns defined for index '" + indexInfo.name() + "' for column '" + columnName
                    + "' do match the columns defined by the database server.\n"
                    + Arrays.toString(indexInfo.columns()) + " does not equal " + Arrays.toString(indexSchema.getColumns())
                    + "\nNote: order must be the same.");
        }
    }
}
